//--------------------------------------------------------------------------------------
// File: SoundEffectInstance.cpp
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------

#include "pch.h"
#include "SoundCommon.h"

using namespace DirectX;


//======================================================================================
// SoundEffectInstance
//======================================================================================

// Internal object implementation class.
class SoundEffectInstance::Impl : public IVoiceNotify
{
public:
    Impl( _In_ AudioEngine* engine, _In_ SoundEffect* effect, SOUND_EFFECT_INSTANCE_FLAGS flags ) :
        mBase(),
        mEffect( effect ),
        mWaveBank( nullptr ),
        mIndex( 0 ),
        mLooped( false )
    {
        assert( engine != 0 );
        engine->RegisterNotify( this, false );

        assert( mEffect != 0 );
        mBase.Initialize( engine, effect->GetFormat(), flags );
    }

    Impl( _In_ AudioEngine* engine, _In_ WaveBank* waveBank, uint32_t index, SOUND_EFFECT_INSTANCE_FLAGS flags ) :
        mBase(),
        mEffect( nullptr ),
        mWaveBank( waveBank ),
        mIndex( index ),
        mLooped( false )
    {
        assert( engine != 0 );
        engine->RegisterNotify( this, false );

        char buff[64];
        auto wfx = reinterpret_cast<WAVEFORMATEX*>( buff );
        assert( mWaveBank != 0 );
        mBase.Initialize( engine, mWaveBank->GetFormat( index, wfx, 64 ), flags );
    }

    ~Impl()
    {
        mBase.DestroyVoice();

        if ( mBase.engine )
        {
            mBase.engine->UnregisterNotify( this, false, false );
            mBase.engine = nullptr;
        }
    }

    void Play( bool loop );

    // IVoiceNotify
    virtual void OnBufferEnd() override
    {
        // We don't register for this notification for SoundEffectInstances, so this should not be invoked
        assert( false );
    }

    virtual void OnCriticalError() override
    {
        mBase.OnCriticalError();
    }

    virtual void OnReset() override
    {
        mBase.OnReset();
    }

    virtual void OnUpdate() override
    {
        // We do not register for update notification
        assert(false);
    }

    virtual void OnDestroyEngine() override
    {
        mBase.OnDestroy();
    }

    virtual void OnTrim() override
    {
        mBase.OnTrim();
    }

    virtual void GatherStatistics( AudioStatistics& stats ) const override
    {
        mBase.GatherStatistics(stats);
    }

    SoundEffectInstanceBase         mBase;
    SoundEffect*                    mEffect;
    WaveBank*                       mWaveBank;
    uint32_t                        mIndex;
    bool                            mLooped;
};


void SoundEffectInstance::Impl::Play( bool loop )
{
    if ( !mBase.voice )
    {
        if ( mWaveBank )
        {
            char buff[64];
            auto wfx = reinterpret_cast<WAVEFORMATEX*>( buff );
            mBase.AllocateVoice( mWaveBank->GetFormat( mIndex, wfx, 64) );
        }
        else
        {
            assert( mEffect != 0 );
            mBase.AllocateVoice( mEffect->GetFormat() );
        }
    }

    if ( !mBase.Play() )
        return;

    // Submit audio data for STOPPED -> PLAYING state transition
    XAUDIO2_BUFFER buffer;

#if defined(_XBOX_ONE) || (_WIN32_WINNT < _WIN32_WINNT_WIN8)

    bool iswma = false;
    XAUDIO2_BUFFER_WMA wmaBuffer;
    if ( mWaveBank )
    {
        iswma = mWaveBank->FillSubmitBuffer( mIndex, buffer, wmaBuffer );
    }
    else
    {
        assert( mEffect != 0 );
        iswma = mEffect->FillSubmitBuffer( buffer, wmaBuffer );
    }

#else

    if ( mWaveBank )
    {
        mWaveBank->FillSubmitBuffer( mIndex, buffer );
    }
    else
    {
        assert( mEffect != 0 );
        mEffect->FillSubmitBuffer( buffer );
    }

#endif
    
    buffer.Flags = XAUDIO2_END_OF_STREAM;
    if ( loop )
    {
        mLooped = true;
        buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
    }
    else
    {
        mLooped = false;
        buffer.LoopCount = buffer.LoopBegin = buffer.LoopLength = 0;
    }
    buffer.pContext = nullptr;

    HRESULT hr;
#if defined(_XBOX_ONE) || (_WIN32_WINNT < _WIN32_WINNT_WIN8)
    if ( iswma )
    {
        hr = mBase.voice->SubmitSourceBuffer( &buffer, &wmaBuffer );
    }
    else
#endif
    {
        hr = mBase.voice->SubmitSourceBuffer( &buffer, nullptr );
    }

    if ( FAILED(hr) )
    {
#ifdef _DEBUG
        DebugTrace( "ERROR: SoundEffectInstance failed (%08X) when submitting buffer:\n", hr );

        char buff[64];
        auto wfx = ( mWaveBank ) ? mWaveBank->GetFormat( mIndex, reinterpret_cast<WAVEFORMATEX*>( buff ), 64 )
                                 : mEffect->GetFormat();

        size_t length = ( mWaveBank ) ? mWaveBank->GetSampleSizeInBytes( mIndex ) : mEffect->GetSampleSizeInBytes();

        DebugTrace( "\tFormat Tag %u, %u channels, %u-bit, %u Hz, %Iu bytes\n", wfx->wFormatTag, 
                    wfx->nChannels, wfx->wBitsPerSample, wfx->nSamplesPerSec, length );
#endif
        mBase.Stop( true, mLooped );
        throw std::exception( "SubmitSourceBuffer" );
    }
}


//--------------------------------------------------------------------------------------
// SoundEffectInstance
//--------------------------------------------------------------------------------------

// Private constructors
_Use_decl_annotations_
SoundEffectInstance::SoundEffectInstance( AudioEngine* engine, SoundEffect* effect, SOUND_EFFECT_INSTANCE_FLAGS flags ) :
    pImpl( new Impl( engine, effect, flags ) )
{
}

_Use_decl_annotations_
SoundEffectInstance::SoundEffectInstance( AudioEngine* engine, WaveBank* waveBank, int index, SOUND_EFFECT_INSTANCE_FLAGS flags ) :
    pImpl( new Impl( engine, waveBank, index, flags ) )
{
}


// Move constructor.
SoundEffectInstance::SoundEffectInstance(SoundEffectInstance&& moveFrom)
  : pImpl(std::move(moveFrom.pImpl))
{
}


// Move assignment.
SoundEffectInstance& SoundEffectInstance::operator= (SoundEffectInstance&& moveFrom)
{
    pImpl = std::move(moveFrom.pImpl);
    return *this;
}


// Public destructor.
SoundEffectInstance::~SoundEffectInstance()
{
    if( pImpl )
    {
        if ( pImpl->mWaveBank )
        {
            pImpl->mWaveBank->UnregisterInstance( this );
            pImpl->mWaveBank = nullptr;
        }

        if ( pImpl->mEffect )
        {
            pImpl->mEffect->UnregisterInstance( this );
            pImpl->mEffect = nullptr;
        }
    }
}


// Public methods.
void SoundEffectInstance::Play( bool loop )
{
    pImpl->Play( loop );
}


void SoundEffectInstance::Stop( bool immediate )
{
    pImpl->mBase.Stop( immediate, pImpl->mLooped );
}


void SoundEffectInstance::Pause()
{
    pImpl->mBase.Pause();
}


void SoundEffectInstance::Resume()
{
    pImpl->mBase.Resume();
}


void SoundEffectInstance::SetVolume( float volume )
{
    if ( pImpl->mBase.voice )
    {
        pImpl->mBase.voice->SetVolume( volume );
    }
}


void SoundEffectInstance::SetPitch( float pitch )
{
    pImpl->mBase.SetPitch( pitch );
}


void SoundEffectInstance::SetPan( float pan )
{
    pImpl->mBase.SetPan( pan );
}


void SoundEffectInstance::Apply3D( const AudioListener& listener, const AudioEmitter& emitter )
{
    pImpl->mBase.Apply3D( listener, emitter );
}


// Public accessors.
bool SoundEffectInstance::IsLooped() const
{
    return pImpl->mLooped;
}


SoundState SoundEffectInstance::GetState()
{
    return pImpl->mBase.GetState( true );
}


// Notifications.
void SoundEffectInstance::OnDestroyParent()
{
    pImpl->mBase.OnDestroy();
    pImpl->mWaveBank = nullptr;
    pImpl->mEffect = nullptr;
}